package com.whynot.frame.auth.shiro.session;

/**
 * 描    述:Session会话存入Redis
 * 创建时间: 2018/06/27
 */
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import com.whynot.frame.basic.redis.service.IRedisClient;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 描述: Shiro在SpringBoot下的会话管理:
 * 注意：暂时不使用
 * 作者: chenyao
 * 日期: 2018-01-30.
 * 项目名称: banke-boot-basic
 * 版本: 1.0.0
 * JDK: since 1.8
 */
@Deprecated
public class CachingShiroSessionDao extends CachingSessionDAO {

    private static final Logger logger = LoggerFactory.getLogger(CachingShiroSessionDao.class);

    /**
     * redis session key前缀 
     */
    private final String SHIRO_REDIS_SESSION = "shiro_redis_session:";

    private int globalSessionTimeout= 3600;

    /**
     * jedis  操作redis的封装 
     */
    @Autowired
    private IRedisClient redisClient;


    /**
     * 如DefaultSessionManager在创建完session后会调用该方法；
     * 如保存到关系数据库/文件系统/NoSQL数据库；即可以实现会话的持久化；
     * 返回会话ID；主要此处返回的ID.equals(session.getId())；
     */
    @Override
    protected Serializable doCreate(Session session) {
        // 创建一个Id并设置给Session
        Serializable sessionId = this.generateSessionId(session);
        assignSessionId(session, sessionId);
        this.saveSession(session);
        return sessionId;
    }

    /**
     * 重写CachingSessionDAO中readSession方法，如果Session中没有登陆信息就调用doReadSession方法从Redis中重读
     */
    @Override
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session session = getCachedSession(sessionId);
        if (session == null
                || session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
            session = this.doReadSession(sessionId);
            if (session == null) {
                throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
            } else {
                // 缓存
                cache(session, session.getId());
            }
        }
        return session;
    }


    /**
     * 根据会话ID获取会话
     *
     * @param sessionId 会话ID
     * @return
     */
    @Override
    protected Session doReadSession(Serializable sessionId) {
        ShiroSession shiroSession = null;
        try {
            shiroSession = (ShiroSession)redisClient.getObject(getkey(sessionId));
            if (shiroSession != null
                    && shiroSession.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) != null) {
                //检查session是否过期
                shiroSession.validate();
                // 重置Redis中Session的最后访问时间
                shiroSession.setLastAccessTime(new Date());
                this.saveSession(shiroSession);
                logger.info("sessionId {} name {} 被读取并更新访问时间", sessionId, shiroSession.getClass().getName());
            }
        } catch (Exception e) {
            if (!(e instanceof ExpiredSessionException)) {
                logger.warn("读取Session失败", e);
            }else {
                logger.warn("session已失效:{}", e.getMessage());
            }
        }

        return shiroSession;
    }

    //扩展更新缓存机制，每次请求不重新更新session，更新session会延长session的失效时间
    @Override
    public void update(Session session) throws UnknownSessionException {
        doUpdate(session);
        if (session instanceof ValidatingSession) {
            if (((ValidatingSession) session).isValid()) {
                //不更新ehcach中的session，使它在设定的时间内过期
                //cache(session, session.getId());
                redisClient.expire(getkey(session.getId()), globalSessionTimeout);
            } else {
                uncache(session);
            }
        } else {
            cache(session, session.getId());
        }
    }

    /**
     * 更新会话；如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
     */
    @Override
    protected void doUpdate(Session session) {
        //如果会话过期/停止 没必要再更新了
        try {
            if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
                return;
            }
        } catch (Exception e) {
            logger.error("ValidatingSession error");
        }

        try {
            if (session instanceof ShiroSession) {
                // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
                ShiroSession shiroSession = (ShiroSession) session;
                if (!shiroSession.isChanged()) {
                    return;
                }
                shiroSession.setChanged(false);
                this.saveSession(session);
                logger.info("sessionId {} name {} 被更新", session.getId(), session.getClass().getName());

            } else if (session instanceof Serializable) {
                this.saveSession(session);
                logger.info("sessionId {} name {} 作为非ShiroSession对象被更新, ", session.getId(), session.getClass().getName());
            } else {
                logger.warn("sessionId {} name {} 不能被序列化 更新失败", session.getId(), session.getClass().getName());
            }
        } catch (Exception e) {
            logger.warn("更新Session失败", e);
        }
    }

    /**
     * 删除会话；当会话过期/会话停止（如用户退出时）会调用
     */
    @Override
    protected void doDelete(Session session) {
        try {
            redisClient.remove(getkey(session.getId()));
            logger.debug("Session {} 被删除", session.getId());
        } catch (Exception e) {
            logger.warn("修改Session失败", e);
        }
    }

    /**
     * 删除cache中缓存的Session
     */
    public void uncache(Serializable sessionId) {
        Session session = this.readSession(sessionId);
        super.uncache(session);
        logger.info("取消session {} 的缓存", sessionId);
    }

    /**
     *
     *  统计当前活动的session
     */
    @Override
    public Collection<Session> getActiveSessions() {
        Set<String> keys = redisClient.getKeys(getkey("*"));
        Set<Session> sessions = new HashSet<Session>();
        for(String key:keys){
            Session session = (Session)redisClient.getObject(key);
            sessions.add(session);
        }

        return sessions;
    }

    /**
     * save session
     * @param session
     * @throws UnknownSessionException
     */
    private void saveSession(Session session) throws UnknownSessionException{
        if(session == null || session.getId() == null){
            logger.error("session or session id is null");
            return;
        }
        redisClient.setexObject(getkey(session.getId()), session, globalSessionTimeout);
    }


    private String getkey(Serializable sessionId) {
        return this.SHIRO_REDIS_SESSION + sessionId;
    }
}